﻿using System;
using System.Globalization;
using System.Reflection;
using MessagingWPF.Infrastructure.Utilities;

namespace MessagingWPF.Infrastructure
{
	/// <summary>
	/// A subscription abstraction.
	/// </summary>
	[Serializable]
	public abstract class Subscription : IEquatable<Subscription>
	{
		/// <summary>
		/// Initializes a new instance of the class.
		/// </summary>
		/// <param name="messageType">The message type this subscription is interested in.</param>
		protected Subscription(Type messageType)
		{
			Ensure.ArgumentIsNotNull(messageType, "messageType");

			MessageType = messageType;
		}

		/// <summary>
		/// Gets the message type this subscription is interested in.
		/// </summary>
		public Type MessageType { get; private set; }

		#region IEquatable<Subscription> Members

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <param name="other">An object to compare with this object.</param>
		/// <returns>
		/// <c>True</c> if the current object is equal to the <paramref name="other"/> parameter;
		/// otherwise, <c>false</c>.
		/// </returns>
		public bool Equals(Subscription other)
		{
			return other != null &&
				EqualsCore(other);
		}

		#endregion

		/// <summary>
		/// Sends the message to the subscriber.
		/// </summary>
		/// <param name="message">The message to be sent.</param>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="message"/> is <c>null</c>
		/// </exception>
		public void SendMessage(object message)
		{
			Ensure.ArgumentIsNotNull(message, "message");

			SendMessageCore(message);
		}

		/// <summary>
		/// Determines whether the specified object is equal to the current object.
		/// </summary>
		/// <param name="obj">
		/// The object to compare with the current object.
		/// </param>
		/// <returns>
		/// <c>True</c> if the specified object is equal to the current object; otherwise, false.
		/// </returns>
		/// <exception cref="T:System.NullReferenceException">
		/// Thrown when the <paramref name="obj"/> is <c>null</c>.
		/// </exception>
		public override bool Equals(object obj)
		{
			var other = obj as Subscription;

			return other != null &&
				Equals(other);
		}

		/// <summary>
		/// Serves as a hash function for a particular type.
		/// </summary>
		/// <returns>
		/// A hash code for the current object.
		/// </returns>
		public abstract override int GetHashCode();

		/// <summary>
		/// Indicates whether the subscription is equal to the specified subscription.
		/// </summary>
		/// <param name="subscription">A subscription to compare with this subscription.</param>
		/// <returns>
		/// <c>True</c> if the current object is equal to the <paramref name="subscription"/>
		/// parameter; otherwise, <c>false</c>.
		/// </returns>
		protected abstract bool EqualsCore(Subscription subscription);

		/// <summary>
		/// Core implementation of sending the message.
		/// </summary>
		/// <param name="message">The message.</param>
		protected abstract void SendMessageCore(object message);

		/// <summary>
		/// Gets the handle method.
		/// </summary>
		/// <param name="subscriberType">Type of the subscriber.</param>
		/// <returns>A <see cref="MethodInfo"/> of the "Handle" method.</returns>
		protected MethodInfo GetHandleMethod(Type subscriberType)
		{
			Ensure.ArgumentIsNotNull(subscriberType, "subscriberType");

			/* TODO: Consider design without the need to specify the method name as string. */

			var methodInfo = subscriberType.GetMethod(
				"Handle",
				new[] {MessageType});

			if (methodInfo == null)
			{
				throw new InvalidOperationException(string.Format(
					CultureInfo.InvariantCulture,
					"Type '{0}' defines no subscription for the message type '{1}'.",
					subscriberType.Name,
					MessageType.Name));
			}

			return methodInfo;
		}
	}
}